#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" This provides the api implementation for the tack_default module.

This module defines code for the most basic tack apis.  We define them
separately here (outside of the tack package) to draw a sharp
distinction between the tack package and the modules installed in a
site.

Refer to the urls.py file for usage.
"""
import _strptime ## WTF? -- getting import errors in some requests, this seems to fix it.
from datetime import datetime, timedelta
from time import gmtime, strftime, strptime

from tack.lib import default403, default500, status_codes, content_headers, string_app
from tack.lib.jsontools import json_dumps
from tack.lib.cipher import base64zip_encode, base64zip_decode, make_ciphers
from tack.db import query

from tack_default.api.login import get_roles


expiry_format = '%a, %d-%b-%y %H:%M:%S GMT'
time_key_format = '%Y-%m-%d %H:%M:%S'
plain = content_headers.default_plain()


def dispatcher_urls(environ, start_response):
    urls = [(path, meth, str(app)) for path, meth, app in environ['dispatcher.urls']]
    output = json_dumps(urls, indent=2)
    return string_app(output)(environ, start_response)


def mod_list(environ, start_response):
    allmods = environ['tack.modules']
    actmods = environ['tack.siteconfig']['activemodules']
    mods = [dict(name=n.__name__, active=n.__name__ in actmods)
            for n in allmods]
    output = json_dumps(mods, indent=2)
    return string_app(output)(environ, start_response)


def default_dsn(env):
    return env['tack.siteconfig']['default_dsn']


def base_key(env):
    return env['tack.siteconfig']['auth_key']


def auth_time_key(base, start=None, length=60*90):
    t = start or (datetime.now() + timedelta(0, length)).timetuple()
    return t, strftime(time_key_format, t) + base


def get_login_roles_app(app):
    def inner(environ, start_response):
        c = environ['connection']
        u = environ['authrec']['login']
        environ['authrec']['roles'] = login.get_roles(c, u)
        return app(environ, start_response)
    return inner


def delete_login_cookies(environ, start_response):
    try:
        cookies = environ['cookies']
    except (KeyError, ):
        start_response(status_codes.bad_request, plain)
        return ['Bad request.']
    cookies.pop('auth', '')
    cookies.pop('auth_time', '')
    ## we don't do this next line because we want to maintain login
    ## name on behalf of the user:
    ## cookies.pop('auth_user', '')
    start_response(status_codes.okay, plain)
    return ['Logout successful.']


def set_login_cookies_app(sources):
    def set_login_cookies(environ, start_response):
        try:
            form, cookies = environ['form'], environ['cookies']
            username, password = form['username'], form['password']
        except (KeyError, ):
            start_response(status_codes.bad_request, plain)
            return ['Malformed login request.']
        try:
            code, msg = status_codes.bad_request, 'Login attempt failed.'
            for source in sources:
                if source(username, password, environ):
                    auth_time, auth_key = auth_time_key(base_key(environ))
                    encrypt, decrypt, cryptcmp = make_ciphers(auth_key)
                    value = base64zip_encode(encrypt(username))
                    expires = strftime(expiry_format, auth_time)
                    atime = base64zip_encode(strftime(expiry_format, auth_time))
                    cookies.update(
                        auth=value,
                        auth_time=atime,
                        auth_user=username)
                    start_response(status_codes.okay, plain)
                    code, msg = status_codes.okay, 'Login successful.'
                    break
                msg += ' Failed on ' + str(source)
        except (Exception, ):
            start_response(status_codes.bad_request, plain)
            return ['Invalid login attempt.']
        start_response(code, plain)
        return [msg]
    return set_login_cookies


def get_auth_user(env):
    cm = env['cookies']
    auth, auth_time, auth_user = cm['auth'], cm['auth_time'], cm['auth_user']
    expr_time = strptime(base64zip_decode(auth_time), expiry_format)
    a_time, a_key = auth_time_key(base_key(env), start=expr_time)
    encrypt, decrypt, cryptcmp = make_ciphers(a_key)
    user = decrypt(base64zip_decode(auth))
    return user, auth_user


def cookie_auth_app(app):
    def cookie_app(environ, start_response):
        try:
            user, auth_user = get_auth_user(environ)
        except (Exception, ), ex:
            start_response(status_codes.bad_request, plain)
            return ['Bad request. %s' % ex]
        else:
            if user != auth_user:
                start_response(status_codes.bad_request, plain)
                return ['Bad auth.']
        environ['authrec'] = {'login':user}
        return app(environ, start_response)
    return cookie_app


def get_login_cookies(environ, start_response):
    output = environ.get('authrec')
    return string_app(json_dumps(output))(environ, start_response)


def get_login_roles(environ, start_response):
    login = environ.get('authrec', {}).get('login', '')
    sql = "SELECT role_name FROM auth_login_group_roles WHERE login = '%s'" % login
    output = query(environ['connection'], sql)
    output = [row[0] for row in output]
    return string_app(json_dumps(output))(environ, start_response)

def set_login_roles_app(app):
    def set_login_roles(environ, start_response):
        login = environ.get('authrec', {}).get('login', '')
        sql = "SELECT role_name FROM auth_login_group_roles WHERE login = '%s'" % login
        output = query(environ['connection'], sql)
        output = [row[0] for row in output]
        environ.get('authrec',{})['roles'] = output
        return app(environ,start_response)
    return set_login_roles


def require_roles_app(app, required=(), handle403=default403):
    required = set(required)
    def require_roles(environ, start_response):
        login_roles = set(environ.get('authrec', {}).get('roles', []))
        if (required & login_roles) == required:
            return app(environ, start_response)
        else:
            return handle403(environ, start_response)
    return require_roles


def k_checker(environ, start_response):
    return string_app('')(environ, start_response)
